#include "widget.h"
#include "ui_widget.h"
#include "pane.h"
#include <QLayout>
#include <QPushButton>
#include <QGridLayout>
#include <QFrame>
#include <QColor>
#include <QLabel>
#include <QVBoxLayout>
#include <QLineEdit>
#include <QRandomGenerator>
#include <QGraphicsGridLayout>
#include <QGraphicsLinearLayout>
#include <QDebug>
#include <QTextEdit>
#include <QDateTime>
#include <QImageReader>
#include <QApplication>
#include <QScreen>
#include <QPushButton>
#include <QStyle>
#include <QTcpServer>
#include <QTcpSocket>
#include <QNetworkInterface>
#include <QAction>
#include <QMenu>
#include <QDialog>
#include <QFormLayout>
#include <QSpinBox>
#include <QDialogButtonBox>
#include <QProcess>
#include <QMessageBox>
#include "weatherclient.h"         // ← 用你前面那份精简版
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QUrlQuery>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
#include <QSslConfiguration>        // 若你还需要临时关闭校验
#include <QMenu>
#include <QTextEdit>
#include <QRegularExpression>
#include <QLocale>

// 辅助函数：取面板的“主体”部位（有标题时是索引最后那个，无标题时就是唯一子件）
static QLineEdit* addValueLine(QWidget* paneBodyWidget,const QString& objName,const QString& valueWithUnit);

// 默认阈值（与你之前的告警一致）
static int sHum   = 88;    // 湿度阈值（%）
static int sTemp  = 33;    // 温度阈值（℃）
static int sLight = 700;  // 光照阈值（lux）
static int sco   = 78;   // CO₂ 阈值（ppm）


Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    // 先构建 UI（里头会创建 btnSettings 按钮）
    InitWidget(this);

    // ===== 修改：改用 SimpleServer，频率=客户端发送频率 =====
    if (!m_server) m_server = new SimpleServer(this);
     //m_server->start(QHostAddress::AnyIPv4, 12345);
    m_server->start(QHostAddress(QStringLiteral("192.168.129.205")), 12345);

    connect(m_server, &SimpleServer::dataReceived,
            this,     &Widget::onSensorData);

    // ===== 设置按钮：弹出“设置列表”（QMenu）=====
    auto menuSettings = new QMenu(this);

    QFont mf = menuSettings->font();
    mf.setPointSize(qMax(14, mf.pointSize()));
    menuSettings->setFont(mf);
    menuSettings->setStyleSheet(
        "QMenu{background:rgba(0,0,0,180);color:#fff;border:1px solid rgba(255,255,255,40);} "
        "QMenu::item:selected{background:rgba(255,255,255,45);} "
    );

    QAction* actEditThreshold = menuSettings->addAction(QStringLiteral("修改报警阈值"));

    // —— 新增：修改天气地址 —— //
    QAction* actChangeWx = menuSettings->addAction(QStringLiteral("修改天气地址"));

    menuSettings->addSeparator();
    QAction* actExit   = menuSettings->addAction(QStringLiteral("退出系统"));

    // 点击按钮 → 在按钮正下方弹出菜单
    connect(btnSettings, &QPushButton::clicked, this, [=]{
        const QPoint gpos = btnSettings->mapToGlobal(QPoint(0, btnSettings->height()));
        menuSettings->popup(gpos);
    });

    connect(actEditThreshold, &QAction::triggered, this, [this]{ showThresholdDialog(); });

    // 新增：修改天气地址
    connect(actChangeWx, &QAction::triggered, this, [this]{ showWeatherLocationDialog(); });

    QAction* actEditIntro = menuSettings->addAction(QStringLiteral("更改简介"));  // ★ 新增
    connect(actEditIntro, &QAction::triggered, this, &Widget::showEditIntroDialog); // ★ 新增

    // 新增：退出
    connect(actExit, &QAction::triggered, this, [this]{
        auto ret = QMessageBox::question(this, QStringLiteral("确认退出"),
                                         QStringLiteral("确定要退出系统吗？这将关闭应用程序。"));
        if (ret == QMessageBox::Yes) {
            qInfo().noquote() << "[UI] 退出系统（退出应用）";
            qApp->quit();
        }
    });


    connect(m_server, &SimpleServer::deviceBitsReceived,
            this,      &Widget::onDeviceBits);





    // 例：本地/公有 broker；按你的环境填 host/port/账号密码
    m_mqtt = new mqtt(this,
                      /*hostname*/ QStringLiteral("120.26.201.192"), // 或 broker.hivemq.com / iot.xxx.com
                      /*id*/       m_farmId,
                      /*port*/     1883,
                      /*con_co*/  sco,   /*con_tem*/ sTemp,
                      /*con_hum*/  sHum,    /*con_light*/ sLight);

    // 如需鉴权：
    // m_mqtt->client()->setUsername("xxx");
    // m_mqtt->client()->setPassword("yyy");
    // 如需 TLS：见文末“可选：TLS”

    // 上报上线
    const QString now = QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm:ss");
    m_mqtt->sendOnline(m_farmId, "online", now);

    // 程序退出前上报下线（可选）
    connect(qApp, &QCoreApplication::aboutToQuit, this, [this]{
        if (m_mqtt) {
            const QString t = QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm:ss");
            m_mqtt->sendOnline(m_farmId, "offline", t);
        }
    });   // ★ 先闭合 connect


    // Widget 构造函数内（m_mqtt 创建之后）
    connect(m_mqtt, &mqtt::constantValueReceived, this,
            [this](int farm_id, double co2, double temperature, double humidity,
                   double light_intensity, const QString &ts)
    {
        if (farm_id != m_farmId) return;

        // 1) 更新本地报警阈值（取整到 int，单位保持你现有 UI 的单位）
        sHum   = qRound(humidity);
        sTemp  = qRound(temperature);
        sLight = qRound(light_intensity);
        sco    = qRound(co2);

        qInfo().noquote() << "[MQTT] thresholds updated from cloud:"
                          << "hum=" << sHum
                          << ", temp=" << sTemp
                          << ", light=" << sLight
                          << ", co=" << sco;

        // 2) （可选）在报警面板 w6 加一条日志
        if (QWidget* w6Pane = this->findChild<QWidget*>("w6")) {
            if (QTextEdit* log = paneBody(w6Pane)->findChild<QTextEdit*>("w6_log")) {
                const QString line = QStringLiteral("云端阈值更新：湿度=%1%% 温度=%2℃ 光照=%3lux 一氧化碳=%4ppm")
                                     .arg(sHum).arg(sTemp).arg(sLight).arg(sco);
                log->append(QStringLiteral("[%1] %2")
                            .arg(QTime::currentTime().toString("HH:mm:ss"), line));
            }
        }

        // 3) 下发给所有 TCP 客户端（下位机）——你现有的接口
        if (m_server) {
            const int n = m_server->sendCsvToAll(sHum, sTemp, sLight, sco);
            qInfo().noquote() << "[Server] pushed new thresholds to" << n << "client(s).";
        }

        // 4) （可选）把当前最新阈值再回传到云端，形成最终一致
        if (m_mqtt) {
            const QString now = QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm:ss");
            m_mqtt->sendLimitData(m_farmId, sco, sTemp, sHum, sLight, now);
        }
    });

    // 启动推流
    startRtmpPush();

    // 程序退出前：停推流
    connect(qApp, &QCoreApplication::aboutToQuit, this, [this]{
        stopRtmpPush();
    });
}

Widget::~Widget()
{
    delete ui;
}

void Widget::InitWidget(QWidget *parent)
{
    parent->showFullScreen();
    setContentsMargins(0,0,0,0);

    setObjectName("root");
    setAttribute(Qt::WA_StyledBackground, true);

    setStyleSheet(
        "#root {"
        "  border: none;"
        "  border-image: url(:/icon/cb4997c7e74b0fd71f104773d327e001.jpeg) 0 0 0 0 stretch stretch;"
        "}"
        "#titleBar {"
        "  background: qlineargradient(x1:0,y1:0,x2:1,y2:0, stop:0 rgba(0,187,255,200), stop:1 rgba(0,22,70,220));"
        "  border-bottom: 1px solid rgba(255,255,255,40);"
        "}"
        "#titleBar QLabel {"
        "  color: #ffffff; font-size: 16px; font-weight: 700; margin: 0;"
        "}"
        "#titleBar QPushButton#btnSettings { background: transparent; border: none; border-radius: 12px; }"
        "#titleBar QPushButton#btnSettings:hover { background: rgba(255,255,255,40); }"
        "#titleBar QPushButton#btnSettings:pressed{ background: rgba(255,255,255,60); }"
    );

    auto root = new QVBoxLayout();
    root->setContentsMargins(0,0,0,0);
    root->setSpacing(0);
    parent->setLayout(root);

    // ===== 标题栏 =====
    QWidget* titleBar = new QWidget(parent);
    titleBar->setObjectName("titleBar");
    titleBar->setAttribute(Qt::WA_StyledBackground, true);
    titleBar->setFixedHeight(26);   // 如字体偏大可改为 30~32

    auto titleLay = new QHBoxLayout(titleBar);
    titleLay->setContentsMargins(12, 0, 8, 0);
    titleLay->setSpacing(6);

    // 左侧：设置按钮
    btnSettings = new QPushButton(titleBar);
    btnSettings->setObjectName("btnSettings");
    btnSettings->setToolTip(QStringLiteral("设置"));
    btnSettings->setCursor(Qt::PointingHandCursor);
    btnSettings->setFlat(true);
    btnSettings->setFocusPolicy(Qt::NoFocus);
    btnSettings->setFixedSize(24, 24);
    btnSettings->setIcon(style()->standardIcon(QStyle::SP_FileDialogDetailedView));
    btnSettings->setIconSize(QSize(20,20));
    titleLay->addWidget(btnSettings, 0, Qt::AlignVCenter);

    // 中间：标题
    titleLay->addStretch(1);
    auto lbTitle = new QLabel(QStringLiteral("智能农场"), titleBar);
    lbTitle->setAlignment(Qt::AlignCenter);
    lbTitle->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
    titleLay->addWidget(lbTitle, 1);
    titleLay->addStretch(1);

    // 右侧：时钟（24 小时制，形如 14：42）
    auto lbClock = new QLabel(titleBar);
    lbClock->setObjectName("titleClock");
    lbClock->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
    lbClock->setMinimumWidth(60);
    titleLay->addWidget(lbClock, 0, Qt::AlignVCenter);

    // 时钟刷新
    auto updateClock = [lbClock](){
        QString t = QTime::currentTime().toString("HH:mm");
        t.replace(":", QStringLiteral("：")); // 全角冒号
        lbClock->setText(t);
    };
    updateClock();
    auto clockTimer = new QTimer(titleBar);
    clockTimer->setInterval(1000);
    connect(clockTimer, &QTimer::timeout, lbClock, [lbClock]{   // 以 lbClock 为上下文，自动析构
        QString t = QTime::currentTime().toString("HH:mm");
        t.replace(":", QStringLiteral("："));
        lbClock->setText(t);
    });
    clockTimer->start();

    // ★ 只插一次，保证在最上方
    root->insertWidget(0, titleBar);
    btnSettings->raise();

    setAttribute(Qt::WA_TranslucentBackground, false);
    setAutoFillBackground(true);

    // ===== 中央内容区 =====
    QWidget* center = new QWidget(parent);
    auto grid = new QGridLayout(center);
    grid->setContentsMargins(0,0,0,0);
    grid->setSpacing(0);
    center->setLayout(grid);
    root->addWidget(center, 1);

    // 默认地区（与成员默认值一致）
    m_cityLabel = QStringLiteral("成都");
    m_lat = 30.5728;  m_lon = 104.0668;

    // 9 个面板
    auto w1 = MAKE_GRAD(center, "w1", (m_cityLabel + QStringLiteral("天气")).toUtf8().constData());
    auto w2 = MAKE_GRAD(center, "w2", "实时数据");
    auto w3 = MAKE_GRAD(center, "w3", "光照强度");
    auto w4 = MAKE_GRAD(center, "w4", "实时监控");
    auto w5 = MAKE_GRAD(center, "w5", "基地图片");
    auto w6 = MAKE_GRAD(center, "w6", "报警信息");
    auto w7 = MAKE_GRAD(center, "w7", "设备状态");
    auto w9 = MAKE_GRAD(center, "w9", "基地介绍");

    grid->addWidget(w1, 0, 0, 1, 1);
    grid->addWidget(w2, 1, 0, 1, 1);
    grid->addWidget(w3, 2, 0, 1, 1);
    grid->addWidget(w4, 0, 1, 2, 2);
    grid->addWidget(w5, 2, 1, 1, 1);
    grid->addWidget(w6, 0, 3, 1, 1);
    grid->addWidget(w7, 1, 3, 1, 1);
    grid->addWidget(w9, 2, 2, 1, 2);

    // 列：让右侧（w6/w7）更窄
    grid->setColumnStretch(0, 3);  // 左列（w1/w2/w3）
    grid->setColumnStretch(1, 5);  // 中列-左（w4）
    grid->setColumnStretch(2, 5);  // 中列-右（w4/w9）


    // 行：让中间一行（w2 / w7 所在行）更矮
    grid->setRowStretch(0, 3);     // 顶行（w1 / w4 上半）
    grid->setRowStretch(1, 1);     // 中行（w2 / w7）      ← 更矮
    grid->setRowStretch(2, 3);     // 底行（w3 / w5 / w8）

    // 子模块
    InitW1(w1);
    InitW2(w2);
    InitW3(w3);
    InitW4(w4);
    InitW5(w5);
    InitW6(w6);
    InitW7(w7);
    InitW9(w9);

    line = new QLineSeries;
}



QWidget* Widget::paneBody(QWidget* pane)
{
    if (!pane || !pane->layout()) return nullptr;

    auto vbox = qobject_cast<QVBoxLayout*>(pane->layout());
    if (!vbox || vbox->count() == 0) return nullptr;

    auto item = vbox->itemAt(vbox->count() - 1);
    return item ? item->widget() : nullptr;
}

static QLineEdit* addValueLine(QWidget* paneBodyWidget,
                               const QString& objName,
                               const QString& valueWithUnit)
{
    if (!paneBodyWidget) return nullptr;

    // 若主体还没布局，给它一个垂直布局
    auto vbox = qobject_cast<QVBoxLayout*>(paneBodyWidget->layout());
    if (!vbox) {
        vbox = new QVBoxLayout(paneBodyWidget);
        paneBodyWidget->setLayout(vbox);
    }

    auto le = new QLineEdit(paneBodyWidget);
    le->setObjectName(objName);
    le->setReadOnly(true);
    le->setAlignment(Qt::AlignCenter);

    // 字体 & 样式
    QFont f = le->font();
    f.setPointSize(12);   // ==========================小模块数据字体大小改这里
    f.setBold(true);
    le->setFont(f);

    le->setStyleSheet(
        "background: rgba(255,255,255,20);"
        "color: rgb(85, 250, 247);"             //==========================小模块数据字体颜色改这里
        "border: 1px solid rgba(255,255,255,60);"
        "border-radius: 12px;"
        "padding: 6px;"
    );

    le->setText(valueWithUnit);

    // 居中摆放
    vbox->addStretch(1);
    vbox->addWidget(le, 0, Qt::AlignCenter);
    vbox->addStretch(2);
    return le;
}

// 更瘦的状态胶囊
static void setOnOffLabel(QLabel* lb, bool on) {
    if (!lb) return;
    lb->setText(on ? QStringLiteral("开") : QStringLiteral("关"));
    lb->setAlignment(Qt::AlignCenter);
    lb->setFixedWidth(34);                  // 更窄
    lb->setStyleSheet(QStringLiteral(
        "QLabel {"
        "  padding:1px 6px;"               // 更小的内边距
        "  border-radius:9px;"
        "  border:1px solid rgba(255,255,255,80);"
        "  color:%1; background:%2;"
        "}"
    ).arg(on ? "#00e676" : "rgba(255,255,255,200)")
     .arg(on ? "rgba(0,230,118,0.18)" : "rgba(255,255,255,0.10)"));
}


void Widget::InitW1(QWidget *parent)
{
    QWidget* body = paneBody(parent);
    if (!body) return;

    auto h = new QHBoxLayout(body);
    h->setSpacing(0);
    body->setLayout(h);

    const int FS_DATE    = 10;
    const int FS_TEMP    = 18;
    const int FS_WEATHER = 12;
    const int FS_INFO    = 12;
    const int FS_PIC     = 10;

    QWidget* left = new QWidget(body);
    auto lv = new QVBoxLayout(left);
    lv->setContentsMargins(0,0,0,0);
    lv->setSpacing(1);

    auto lbDate = new QLabel(QStringLiteral("--/-- 星期—"), left);
    lbDate->setObjectName("w1_date");
    { QFont f = lbDate->font(); f.setPointSize(FS_DATE); f.setBold(true); lbDate->setFont(f); }
    lbDate->setStyleSheet("color:#E0FFF7");

    auto lbTemp = new QLabel(QStringLiteral("—℃"), left);
    lbTemp->setObjectName("w1_temp");
    { QFont f = lbTemp->font(); f.setPointSize(FS_TEMP); f.setBold(true); lbTemp->setFont(f); }
    lbTemp->setStyleSheet("color:#E0FFF7;");

    auto lbWeather = new QLabel(QStringLiteral("—"), left);
    lbWeather->setObjectName("w1_weather");
    { QFont f = lbWeather->font(); f.setPointSize(FS_WEATHER); lbWeather->setFont(f); }
    lbWeather->setStyleSheet("color:#E0FFF7;");

    lv->addWidget(lbDate,    0, Qt::AlignLeft|Qt::AlignVCenter);
    lv->addWidget(lbTemp,    0, Qt::AlignLeft|Qt::AlignVCenter);
    lv->addWidget(lbWeather, 0, Qt::AlignLeft|Qt::AlignVCenter);
    lv->addStretch(1);

    QWidget* mid = new QWidget(body);
    auto mv = new QVBoxLayout(mid);
    mv->setContentsMargins(0,0,0,0);
    mv->setSpacing(0);

    auto lbPic = new QLabel(mid);
    lbPic->setObjectName("w1_pic");
    lbPic->setAlignment(Qt::AlignCenter);
    lbPic->setMinimumSize(40,40);
    { QFont f = lbPic->font(); f.setPointSize(FS_PIC); lbPic->setFont(f); }
    lbPic->setText(QStringLiteral("（图片）"));
    lbPic->setStyleSheet("QLabel{ background: transparent; border: none; color: rgba(255,255,255,120);}");

    mv->addWidget(lbPic);

    QWidget* right = new QWidget(body);
    auto rv = new QVBoxLayout(right);
    rv->setContentsMargins(0,0,0,0);
    rv->setSpacing(2);

    auto mk = [&](const char* obj)->QLabel*{
        auto lb = new QLabel(QStringLiteral("—"), right);
        lb->setObjectName(obj);
        QFont f = lb->font(); f.setPointSize(FS_INFO); f.setBold(true); lb->setFont(f);
        lb->setStyleSheet("color:#E0FFF7;");
        lb->setAlignment(Qt::AlignLeft|Qt::AlignVCenter);
        return lb;
    };
    auto lbAQI  = mk("w1_aqi");
    auto lbWind = mk("w1_wind");
    auto lbHum  = mk("w1_hum");
    lbAQI->setText(QStringLiteral("空气 —"));
    lbWind->setText(QStringLiteral("—风"));
    lbHum->setText(QStringLiteral("湿度 —"));

    rv->addWidget(lbAQI);
    rv->addWidget(lbWind);
    rv->addWidget(lbHum);
    rv->addStretch(1);

    h->addWidget(left, 1);
    h->addWidget(mid,  1);
    h->addWidget(right,1);
    h->setStretch(0, 3);
    h->setStretch(1, 4);
    h->setStretch(2, 3);

    // ===== 天气数据源：WeatherClient（成员化） =====
    m_weatherClient = new WeatherClient(this);
    m_weatherClient->setObjectName("w1_weather_client");
    m_weatherClient->setTimeoutMs(10000);

    connect(m_weatherClient, &WeatherClient::gotWeather, this, [this](const WeatherData& d){
        // 标题：XX天气
        if (!d.city.isEmpty()) {
            m_cityLabel = d.city; // 以接口返回为准
            if (auto w1Pane = this->findChild<QWidget*>("w1")) {
                // 尝试在 w1 的标题栏找到 QLabel 并更新
                if (auto vbox = qobject_cast<QVBoxLayout*>(w1Pane->layout())) {
                    if (vbox->count() > 0) {
                        if (QWidget* titleArea = vbox->itemAt(0)->widget()) {
                            if (auto titleLbl = titleArea->findChild<QLabel*>()) {
                                titleLbl->setText(m_cityLabel + QStringLiteral("天气"));
                            }
                        }
                    }
                }
            }
        }

        // 左列：日期/温度/天气
        QDate date = QDate::currentDate();
        if (!d.updatedAt.isEmpty()) {
            const QDateTime dt = QDateTime::fromString(d.updatedAt, "yyyy-MM-dd HH:mm:ss");
            if (dt.isValid()) date = dt.date();
        }
        QString weekday = d.weekday;
        if (weekday.startsWith(QStringLiteral("周"))) weekday.replace(0,1, QStringLiteral("星期"));
        if (weekday.isEmpty()) weekday = QStringLiteral("星期—");

        if (auto lb = findChild<QLabel*>("w1_date"))
            lb->setText(QString::number(date.month()) + "/" + QString::number(date.day()) + " " + weekday);
        if (auto lb = findChild<QLabel*>("w1_temp"))
            lb->setText(QString::number(qRound(d.temperature)) + QStringLiteral("℃"));
        if (auto lb = findChild<QLabel*>("w1_weather"))
            lb->setText(d.weatherText.isEmpty()? QStringLiteral("—") : d.weatherText);

        // 右列：风向/湿度
        if (auto lb = findChild<QLabel*>("w1_wind"))
            lb->setText(d.windDirectionText.isEmpty()? QStringLiteral("—风") : d.windDirectionText + QStringLiteral("风"));
        if (auto lb = findChild<QLabel*>("w1_hum"))
            lb->setText(QStringLiteral("湿度 ") + (d.humidity < 0 ? QStringLiteral("—") : QString::number(d.humidity) + "%"));

        // 中列图标
        auto iconNameFromCode = [](int code) -> QString {
            switch (code) {
                case 0: return QStringLiteral("晴");
                case 1: case 2: case 3: return QStringLiteral("多云");
                case 61: return QStringLiteral("小雨");
                case 63: return QStringLiteral("中雨");
                case 65: return QStringLiteral("大雨");
                case 71: return QStringLiteral("小雪");
                case 73: return QStringLiteral("中雪");
                case 75: return QStringLiteral("大雪");
                case 95: case 96: case 99: return QStringLiteral("雷阵雨");
                default: return QStringLiteral("多云");
            }
        };
        if (auto pic = findChild<QLabel*>("w1_pic")) {
            const QString name = iconNameFromCode(d.weatherCode);
            const QString path = QStringLiteral(":/icon/%1.png").arg(name);
            QPixmap pm(path);
            if (!pm.isNull()) {
                pic->setScaledContents(true);
                pic->setPixmap(pm);
                pic->setToolTip(name);
            } else {
                pic->setPixmap(QPixmap());
                pic->setText(QStringLiteral("（无图片：%1）").arg(name));
            }
        }
    });
    connect(m_weatherClient, &WeatherClient::error, this, [this](const QString& msg, int code){
        if (auto lb = findChild<QLabel*>("w1_weather"))
            lb->setText(QStringLiteral("获取失败(%1)").arg(code));
        qWarning() << "[w1] weather error:" << code << msg;
    });

    // ===== 定时刷新 + 首次拉取 =====
    auto timer = new QTimer(this);
    timer->setObjectName("w1_timer");
    timer->setInterval(600 * 1000);  // ★ 每 10min 刷新

    auto updateOnce = [this](){
        if (m_weatherClient)
            m_weatherClient->fetchByCoordinates(m_lat, m_lon, m_cityLabel, "zh");
        fetchAQI(m_lat, m_lon); // AQI 也一起刷

        // 本地日期先刷新一次
        const QDate d = QDate::currentDate();
        QString weekday = QStringLiteral("星期—");
        switch (d.dayOfWeek()) {
            case 1: weekday = QStringLiteral("星期一"); break;
            case 2: weekday = QStringLiteral("星期二"); break;
            case 3: weekday = QStringLiteral("星期三"); break;
            case 4: weekday = QStringLiteral("星期四"); break;
            case 5: weekday = QStringLiteral("星期五"); break;
            case 6: weekday = QStringLiteral("星期六"); break;
            case 7: weekday = QStringLiteral("星期日"); break;
        }
        if (auto lb = findChild<QLabel*>("w1_date"))
            lb->setText(QString::number(d.month()) + "/" + QString::number(d.day()) + " " + weekday);
    };
    connect(timer, &QTimer::timeout, this, updateOnce);
    timer->start();
    updateOnce();
}



void Widget::InitW2(QWidget *parent)
{
    // 1) 取到 w2 的主体区域（不是标题栏）
    QWidget* body = paneBody(parent);
    if (!body) return;

    // 2) 只在 body 上设置你的网格布局
    auto grid = new QGridLayout(body);
    grid->setContentsMargins(0,0,0,0);
    grid->setSpacing(4);
    body->setLayout(grid);

    // 3) 在 body 里放 2x2 的小面板
    auto a = MAKE_PLAIN(body, "soil_temp", "土壤温度");
    auto b = MAKE_PLAIN(body, "soil_hum",  "土壤湿度");
    auto c = MAKE_PLAIN(body, "light",     "光照强度");
    auto d = MAKE_PLAIN(body, "co",       "一氧化碳");

    a->installEventFilter(this);
    b->installEventFilter(this);
    c->installEventFilter(this);
    d->installEventFilter(this);

    grid->addWidget(a, 0,0);
    grid->addWidget(b, 0,1);
    grid->addWidget(c, 1,0);
    grid->addWidget(d, 1,1);

    grid->setRowStretch(0,1);
    grid->setRowStretch(1,1);
    grid->setColumnStretch(0,1);
    grid->setColumnStretch(1,1);
    // 拿到每个面板的主体
    QWidget* bodyA = paneBody(a);
    QWidget* bodyB = paneBody(b);
    QWidget* bodyC = paneBody(c);
    QWidget* bodyD = paneBody(d);

    // 示例数据（你也可以从传感器更新这些值）
    double soilTemp = 0;
    int    soilHum  = 0;
    int    lightLux = 0;
    int    coPpm   = 0;

    // 写入“数值 + 单位”
    addValueLine(bodyA, "le_soil_temp", QString::number(soilTemp, 'f', 1) + QStringLiteral(" ℃"));
    addValueLine(bodyB, "le_soil_hum",  QString::number(soilHum)          + QStringLiteral(" %"));
    addValueLine(bodyC, "le_light",     QString::number(lightLux)         + QStringLiteral(" lux"));
    addValueLine(bodyD, "le_co",       QString::number(coPpm)           + QStringLiteral(" ppm"));
}

void Widget::InitW6(QWidget *parent)
{
    qDebug()<<"qwe";
    // 1) 取到 w6 的主体区域（不是标题栏）
    QWidget* body = paneBody(parent);
    if (!body) return;

    // 2) 主体里放一个垂直布局（只建一次）
    auto v = qobject_cast<QVBoxLayout*>(body->layout());
    if (!v) {
        v = new QVBoxLayout(body);
        v->setContentsMargins(0, 0, 0, 0);
        v->setSpacing(0);
        body->setLayout(v);
    }
}

static QPixmap loadScaled(const QString &path, const QSize &targetMax)
{
    QImageReader r(path);
    r.setAutoTransform(true);                 // 遵循 EXIF 方向
    const QSize orig = r.size();              // 原图尺寸（若可得）
    if (orig.isValid()) {
        QSize wanted = orig;
        wanted.scale(targetMax, Qt::KeepAspectRatio);  // 等比缩到不超过目标区域
        r.setScaledSize(wanted);              // ★ 解码阶段即输出小图，省内存
    }
    return QPixmap::fromImage(r.read());
}

void Widget::InitW5(QWidget *parent)
{
    QWidget* body = paneBody(parent);
    if (!body) return;

    auto vbox = new QVBoxLayout(body);
    vbox->setContentsMargins(0,0,0,0);
    vbox->setSpacing(0);
    vbox->setSizeConstraint(QLayout::SetMinimumSize);  // ★ 不向外“要大”

    w5Image = new QLabel(body);
    w5Image->setAlignment(Qt::AlignCenter);
    // ★ 关键：不要让原图尺寸影响布局 sizeHint
    w5Image->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored);
    w5Image->setMinimumSize(1,1);
    // 不用 setScaledContents(true)，我们手动按区域缩放
    vbox->addWidget(w5Image);

    // 预置图片路径（确认这些在 .qrc 里）
    w5Images.clear();
    w5Images << ":/icon/14c2c730b6647a5589d7e8f85d0309a8.jpeg"
             << ":/icon/41d359f75c38d1121c38ebac697276aa.jpeg"
             << ":/icon/aa92b1879e3a5795f0b568e934d18a06.jpg";

    auto showIndex = [this, body](int idx){
        if (!w5Image || idx < 0 || idx >= w5Images.size()) return;

        // 以 w5 面板可用区域为上限，最多不超过屏幕 1920x1080
        const QSize paneSize = body->size().isValid() ? body->size()
                                : (qApp->primaryScreen()
                                   ? qApp->primaryScreen()->availableGeometry().size()
                                   : QSize(1280,720));
        const QSize targetMax = paneSize.boundedTo(QSize(1920,1080));

        QPixmap pm = loadScaled(w5Images[idx], targetMax);
        // 再做一次“微调”缩放，确保刚好贴合（保持比例，不拉伸）
        if (!pm.isNull())
            pm = pm.scaled(w5Image->size().isValid() ? w5Image->size() : targetMax,
                           Qt::KeepAspectRatio, Qt::SmoothTransformation);

        w5Image->setPixmap(pm);
    };

    // 初始显示第 0 张
    w5Index = 0;
    if (!w5Images.isEmpty()) showIndex(w5Index);

    // 每 5 秒切换
    if (!w5Timer) w5Timer = new QTimer(this);
    connect(w5Timer, &QTimer::timeout, this, [this, showIndex]{
        if (w5Images.isEmpty()) return;
        w5Index = (w5Index + 1) % w5Images.size();
        showIndex(w5Index);
    });
    w5Timer->start(5000);

    // 面板尺寸变化时，按新尺寸重算一次，避免模糊/拉伸
    body->installEventFilter(this);
}


void Widget::InitW9(QWidget *parent)
{
    QWidget* body = paneBody(parent);
    if (!body) return;

    // 先确保 body 有布局，并把 QTextEdit 加进去
    auto v = qobject_cast<QVBoxLayout*>(body->layout());
    if (!v) {
        v = new QVBoxLayout(body);
        v->setContentsMargins(0,0,0,0);
        v->setSpacing(0);
        body->setLayout(v);
    }

    // 简介框（可滚动）
    QTextEdit* intro = new QTextEdit(body);
    intro->setObjectName("w9_intro");      // 以后“更改简介”要用到
    intro->setReadOnly(true);
    intro->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
    intro->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
    intro->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); // 可选：不横向滚动

    // 样式（在你原有基础上补一丢丢，让滚动条手柄可见）
    intro->setFrameStyle(QFrame::NoFrame);
    intro->setAttribute(Qt::WA_TranslucentBackground, true);
    intro->setAutoFillBackground(false);
    intro->setStyleSheet(
        "QTextEdit{background:transparent; color:#ffffff; border:none; font-size:16px;}"
        "QTextEdit::viewport{background:transparent;}"
        "QScrollBar:vertical{background:transparent; width:10px; margin:0;}"
        "QScrollBar::handle:vertical{background:rgba(255,255,255,120); border-radius:4px; min-height:20px;}"
        "QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical{height:0;}"
        "QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical{background:transparent;}"
    );

    // 保险：有些平台用调色板更稳
    QPalette pal = intro->palette();
    pal.setColor(QPalette::Base, Qt::transparent);
    pal.setColor(QPalette::Text, QColor("#ffffff"));
    intro->setPalette(pal);

    // 默认文本（你的原文）
    intro->setText(
        "       智能大棚用传感器、 自动控制与云端数据分析，"
        "实现对作物生长环境的精细化管理。系统实时采集数据，"
        "并联动设备，维持最优生长区间，降低人工成本。"
    );

    // ★ 关键：把控件放进布局，否则不会显示/不会滚动
    v->addWidget(intro, 1);
}


bool Widget::eventFilter(QObject* watched, QEvent* event)
{
    if (watched == view) {
        switch (event->type()) {
        case QEvent::Resize:
        case QEvent::Show:
        case QEvent::ShowToParent:
        case QEvent::Polish:
        case QEvent::LayoutRequest:
            // 分帧多次矫正，覆盖 Qt 在这些阶段对 plotArea 的改动
            QTimer::singleShot(0,  this, [this]{ updatePlotArea(); });
            QTimer::singleShot(16, this, [this]{ updatePlotArea(); });
            QTimer::singleShot(33, this, [this]{ updatePlotArea(); });
            break;
        case QEvent::MouseButtonPress: {
            auto mouseEvent = static_cast<QMouseEvent*>(event);
            if (mouseEvent->button() == Qt::LeftButton) {
                QStringList titles;
                for (QAbstractSeries* s : chart->series()) {
                    // 如果只想传当前显示的曲线标题，保留下面这行；若想传全部标题，删掉这行判断
                    if (!s->isVisible()) continue;

                    QString t;
                    QVariant p = s->property("title");
                    if (p.isValid())
                        t = p.toString();
                    if (t.isEmpty())
                        t = s->name();

                    if (!t.isEmpty())
                        titles << t;
                }

                if (!titles.isEmpty()) {
                    // 你也可以改成传 QStringList 版本的 w3ClickedHandler 接口
                    w3ClickedHandler(titles.join(", "));
                }
                return true;
            }
            break;
        }
        default:
            break;
        }
    }

    // 再处理 w2 里四个小卡片的点击
    if (event->type() == QEvent::MouseButtonPress) {
        QWidget* pane = qobject_cast<QWidget*>(watched);
        while (pane && !(pane->objectName()=="soil_temp" ||
                         pane->objectName()=="soil_hum"  ||
                         pane->objectName()=="light"     ||
                         pane->objectName()=="co")) {
            pane = pane->parentWidget();
        }

        if (pane) {
            QString newTitle;
            if (pane->objectName()=="soil_temp") newTitle = "土壤温度";
            else if (pane->objectName()=="soil_hum") newTitle = "土壤湿度";
            else if (pane->objectName()=="light")    newTitle = "光照强度";
            else if (pane->objectName()=="co")      newTitle = "一氧化碳";

            if (!newTitle.isEmpty()) {
                handlePaneClicked(newTitle);
                return true;
            }
        }
    }

    // ★ w5 面板自适应：当其 body 改变大小时，按新尺寸重新设置 pixmap
        if (w5Image && watched == w5Image->parentWidget()) {
            if (event->type() == QEvent::Resize) {
                if (!w5Images.isEmpty()) {
                    // 当前索引不变，重新按新大小设置一次
                    int idx = qBound(0, w5Index, w5Images.size()-1);
                    // 复用上面的缩放逻辑
                    QSize paneSize = watched->isWidgetType()
                                     ? static_cast<QWidget*>(watched)->size()
                                     : QSize();
                    const QSize targetMax = (paneSize.isValid() ? paneSize : QSize(1280,720))
                                              .boundedTo(QSize(1920,1080));
                    QPixmap pm = loadScaled(w5Images[idx], targetMax);
                    if (!pm.isNull())
                        pm = pm.scaled(w5Image->size(),
                                       Qt::KeepAspectRatio, Qt::SmoothTransformation);
                    w5Image->setPixmap(pm);
                }
            }
        }

    return QWidget::eventFilter(watched, event);
}


void Widget::w3ClickedHandler(const QString& title)
{
    if("光照强度"==title)
            qDebug()<<title;
}

void Widget::DataWarning(const QString &Name, double data)
{
    // 找到 w6 面板
    QWidget* w6Pane = this->findChild<QWidget*>("w6");
    if (!w6Pane) return;

    QWidget* body = paneBody(w6Pane);
    if (!body) return;

    // 创建日志框
    auto log = body->findChild<QTextEdit*>("w6_log");
    if (!log) {
        // 如果没初始化过，这里建一个
        auto v = qobject_cast<QVBoxLayout*>(body->layout());
        if (!v) {
            v = new QVBoxLayout(body);
            v->setContentsMargins(0,0,0,0);
            v->setSpacing(0);
            body->setLayout(v);
        }
        log = new QTextEdit(body);
        log->setObjectName("w6_log");
        log->setReadOnly(true);
        log->setStyleSheet(
                    "QTextEdit {"
                    " background: rgba(0,0,0,80);"
                    " color: #ffeb3b;"
                    " border: 1px solid rgba(255,255,255,80);  font-size:13px;}");
        v->addWidget(log, 1);
    }



    // 追加一条带时间的报警
    const QString ts = QDateTime::currentDateTime().toString("HH:mm:ss");
    log->append(QStringLiteral("[%1] %2 警告：%3").arg(ts, Name, QString::number(data)));
}

// 修改：由服务器推送的数据驱动曲线与告警；频率=客户端发送频率
void Widget::onSensorData(int hum, int temp, int light, int co)
{
    qInfo().noquote() << "[UI] parsed values hum=" << hum
                      << " temp=" << temp
                      << " light=" << light
                      << " co=" << co;

    // 用当前 xValue 作为横坐标
    int x = xValue;

    // —— 使用客户端发送的数据，替代原随机值 ——
    int yHum   = hum;
    int yTemp  = temp;
    int yLight = light;
    int yco   = co;

        // === 转发到云端（MQTT） ===
        if (m_mqtt) {
            const QString ts = QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm:ss");
            // sendData: (id, co, tem, hum, light, time)
            m_mqtt->sendData(m_farmId, co, temp, hum, light, ts);
        }

    if (seriesSoilHum)  seriesSoilHum->append(x, yHum);
    if (seriesSoilTemp) seriesSoilTemp->append(x, yTemp);
    if (seriesLight)    seriesLight->append(x, yLight);
    if (seriesco)      seriesco->append(x, yco);

    // X 轴滑动窗口
    if (axis_x) {
        if (x >= RANGE+1) axis_x->setRange(x - RANGE, x);
    }

    ++xValue;

    // —— 同步 w2 面板四个小卡片的数值显示 ——
    if (auto leTemp = this->findChild<QLineEdit*>("le_soil_temp"))
        leTemp->setText(QString::number(yTemp)  + QStringLiteral(" ℃"));
    if (auto leHum  = this->findChild<QLineEdit*>("le_soil_hum"))
        leHum->setText(QString::number(yHum)    + QStringLiteral(" %"));
    if (auto leLight= this->findChild<QLineEdit*>("le_light"))
        leLight->setText(QString::number(yLight)+ QStringLiteral(" lux"));
    if (auto leco  = this->findChild<QLineEdit*>("le_co"))
        leco->setText(QString::number(yco)    + QStringLiteral(" ppm"));

    // —— 阈值告警：只在“由正常→超标”的瞬间触发（边沿检测，防止刷屏） ——
    static bool humOver   = false;
    static bool tempOver  = false;
    static bool lightOver = false;
    static bool coOver   = false;

    static bool lTemp  = false;
    static bool lHum   = false;
    static bool lLight= false;
    static bool lco   = false;

    auto edgeRise = [](bool &flag, bool nowOver) -> bool {
        if (!flag && nowOver) { flag = true; return true; }
        if (flag && !nowOver)  { flag = false; }
        return false;
    };
//如果超出阈值，在DataWarning函数添加接口，向客户端发送指令：升温“intemp”降温“detemp”、加湿“inhum”干燥“dehum”、点灯“inlight”关灯“delight”

    if (edgeRise(humOver,   yHum   > sHum))   DataWarning(QStringLiteral("土壤湿度"),  yHum);
    if (edgeRise(tempOver,  yTemp  > sTemp))   DataWarning(QStringLiteral("土壤温度"),  yTemp);
    if (edgeRise(lightOver, yLight > sLight)) DataWarning(QStringLiteral("光照强度"),  yLight);
    if (edgeRise(coOver,   yco   > sco))  DataWarning(QStringLiteral("一氧化碳"),  yco);

    if (edgeRise(lHum,   yHum   < sHum-50))   DataWarning(QStringLiteral("土壤湿度过低"),  yHum);
    if (edgeRise(lTemp,  yTemp  < sTemp-20))   DataWarning(QStringLiteral("土壤温度过低"),  yTemp);
    if (edgeRise(lLight, yLight < sLight/10)) DataWarning(QStringLiteral("光照强度过低"),  yLight);
    if (edgeRise(lco,   yco   < sco/5))  DataWarning(QStringLiteral("一氧化碳过低"),  yco);
}

// —— 新增：报警阈值弹窗 —— //
// 说明：这里用 static 变量保存上次填写的数值；
//      点击“确定”后会把四个值通过 m_server->sendCsvToAll(...) 发送给客户端。
void Widget::showThresholdDialog()
{
    QDialog dlg(this);
    dlg.setWindowTitle(QStringLiteral("修改阈值"));
    dlg.setModal(true);

    // 表单布局 + 4 个 SpinBox
    auto form = new QFormLayout(&dlg);
    form->setContentsMargins(16,16,16,16);
    form->setSpacing(12);

    auto spHum   = new QSpinBox(&dlg);
    spHum->setRange(0, 100);     spHum->setValue(sHum);
    spHum->setSuffix(QStringLiteral(" %"));

    auto spTemp  = new QSpinBox(&dlg);
    spTemp->setRange(-50, 100);  spTemp->setValue(sTemp);
    spTemp->setSuffix(QStringLiteral(" ℃"));

    auto spLight = new QSpinBox(&dlg);
    spLight->setRange(0, 100000); spLight->setValue(sLight);
    spLight->setSuffix(QStringLiteral(" lux"));

    auto spco   = new QSpinBox(&dlg);
    spco->setRange(0, 200000);  spco->setValue(sco);
    spco->setSuffix(QStringLiteral(" ppm"));

    form->addRow(QStringLiteral("湿度阈值"),   spHum);
    form->addRow(QStringLiteral("温度阈值"),   spTemp);
    form->addRow(QStringLiteral("光照阈值"),   spLight);
    form->addRow(QStringLiteral("一氧化碳阈值"), spco);

    // 底部按钮
    auto btns = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, &dlg);
    form->addRow(btns);

    // 点击确定：保存并发送给客户端
    connect(btns, &QDialogButtonBox::accepted, &dlg, [&]{
        sHum   = spHum->value();
        sTemp  = spTemp->value();
        sLight = spLight->value();
        sco   = spco->value();

            // 发送阈值到云端（常量参数数据）
            if (m_mqtt) {
                const QString ts = QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm:ss");
                m_mqtt->sendLimitData(m_farmId, sco, sTemp, sHum, sLight, ts);
            }

        qInfo().noquote() << "[UI] 阈值已更新 =>"
                          << "hum=" << sHum
                          << ", temp=" << sTemp
                          << ", light=" << sLight
                          << ", co=" << sco;

        if (m_server) {
            const int n = m_server->sendCsvToAll(sHum, sTemp, sLight, sco); // CSV+换行
            qInfo().noquote() << "[Server] sent thresholds to" << n << "client(s).";
        }
        dlg.accept();
    });
    connect(btns, &QDialogButtonBox::rejected, &dlg, &QDialog::reject);

    dlg.exec();
}

void Widget::fetchAQI(double lat, double lon)
{
    if (!m_aqiNet) {
        m_aqiNet = new QNetworkAccessManager(this);
        m_aqiNet->setObjectName("w1_aqi_net");
    }

    QUrl url("https://air-quality-api.open-meteo.com/v1/air-quality");
    QUrlQuery q;
    q.addQueryItem("latitude",  QString::number(lat, 'f', 4));
    q.addQueryItem("longitude", QString::number(lon, 'f', 4));
    q.addQueryItem("hourly",    "us_aqi,pm2_5");
    q.addQueryItem("timezone",  "Asia/Shanghai");
    q.addQueryItem("forecast_days", "1");
    url.setQuery(q);

    QNetworkRequest req(url);
    // 如需临时跳过证书校验（排障用，生产请删除）：
    QSslConfiguration conf = QSslConfiguration::defaultConfiguration();
    conf.setPeerVerifyMode(QSslSocket::VerifyNone);
    req.setSslConfiguration(conf);

    QNetworkReply* r = m_aqiNet->get(req);
    connect(r, &QNetworkReply::finished, this, [this, r]{
        if (r->error() != QNetworkReply::NoError) {
            if (auto lb = findChild<QLabel*>("w1_aqi")) lb->setText(QStringLiteral("空气 —"));
            qWarning() << "[w1] AQI error:" << r->error() << r->errorString();
            r->deleteLater();
            return;
        }
        const QByteArray raw = r->readAll();
        r->deleteLater();

        QJsonParseError pe{}; QJsonDocument doc = QJsonDocument::fromJson(raw, &pe);
        if (pe.error != QJsonParseError::NoError || !doc.isObject()) {
            if (auto lb = findChild<QLabel*>("w1_aqi")) lb->setText(QStringLiteral("空气 —"));
            return;
        }
        const QJsonObject hourly = doc.object().value("hourly").toObject();
        const auto usArr = hourly.value("us_aqi").toArray();
        const auto pmArr = hourly.value("pm2_5").toArray();
        const int n = qMax(usArr.size(), pmArr.size());
        double us = -1, pm = -1;
        if (n > 0) {
            if (!usArr.isEmpty()) us = usArr.at(n-1).toDouble(-1);
            if (!pmArr.isEmpty()) pm = pmArr.at(n-1).toDouble(-1);
        }

        auto aqiLabelFromValues = [](double usaqi, double pm25)->QString{

            if (pm25 >= 0) {
                if (pm25 <= 35)   return QStringLiteral("优");
                if (pm25 <= 75)   return QStringLiteral("良");
                if (pm25 <= 115)  return QStringLiteral("一般");
                return QStringLiteral("差");
            }
            return QStringLiteral("—");
        };
        const QString cat = aqiLabelFromValues(us, pm);
        if (auto lb = findChild<QLabel*>("w1_aqi")) lb->setText(QStringLiteral("空气 ") + cat);
    });
}

#include <QDoubleSpinBox>

// 修改天气地址弹窗 + 立即刷新天气并更新 w1 标题为“XX天气”
void Widget::showWeatherLocationDialog()
{
    // 记住上次输入（不依赖于头文件里的成员，避免再次编译错误）
    static QString sCity = QStringLiteral("成都");
    static double  sLat  = 30.5728;
    static double  sLon  = 104.0668;

    // 如果 w1 已有标题，就从标题里拆出默认城市名
    if (auto w1Pane = this->findChild<QWidget*>("w1")) {
        if (auto vbox = qobject_cast<QVBoxLayout*>(w1Pane->layout())) {
            if (vbox->count() > 0) {
                if (QWidget* titleArea = vbox->itemAt(0)->widget()) {
                    if (auto titleLbls = titleArea->findChildren<QLabel*>(); !titleLbls.isEmpty()) {
                        const QString t = titleLbls.first()->text(); // 例如 “成都天气”
                        if (t.endsWith(QStringLiteral("天气")))
                            sCity = t.left(t.size() - QStringLiteral("天气").size()).trimmed();
                    }
                }
            }
        }
    }

    QDialog dlg(this);
    dlg.setWindowTitle(QStringLiteral("修改天气地址"));
    dlg.setModal(true);

    auto form = new QFormLayout(&dlg);
    form->setContentsMargins(16,16,16,16);
    form->setSpacing(12);

    auto edCity = new QLineEdit(&dlg);
    edCity->setText(sCity);

    auto spLat = new QDoubleSpinBox(&dlg);
    spLat->setRange(-90.0, 90.0);
    spLat->setDecimals(6);
    spLat->setSuffix(QStringLiteral(" °"));
    spLat->setValue(sLat);

    auto spLon = new QDoubleSpinBox(&dlg);
    spLon->setRange(-180.0, 180.0);
    spLon->setDecimals(6);
    spLon->setSuffix(QStringLiteral(" °"));
    spLon->setValue(sLon);

    form->addRow(QStringLiteral("地区名"), edCity);
    form->addRow(QStringLiteral("纬度"),   spLat);
    form->addRow(QStringLiteral("经度"),   spLon);

    auto btns = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, &dlg);
    form->addRow(btns);

    connect(btns, &QDialogButtonBox::accepted, &dlg, [&, this]{
        const QString newCity = edCity->text().trimmed();
        const double  newLat  = spLat->value();
        const double  newLon  = spLon->value();

        if (!newCity.isEmpty()) sCity = newCity;
        sLat = newLat; sLon = newLon;

        // 1) 更新 w1 的标题为 “XX天气”
        if (auto w1Pane = this->findChild<QWidget*>("w1")) {
            if (auto vbox = qobject_cast<QVBoxLayout*>(w1Pane->layout())) {
                if (vbox->count() > 0) {
                    if (QWidget* titleArea = vbox->itemAt(0)->widget()) {
                        if (auto titleLbls = titleArea->findChildren<QLabel*>(); !titleLbls.isEmpty()) {
                            titleLbls.first()->setText(sCity + QStringLiteral("天气"));
                        }
                    }
                }
            }
        }

        // 2) 立刻刷新天气（使用 InitW1 里创建并命名的 WeatherClient）
        if (auto wc = this->findChild<WeatherClient*>("w1_weather_client")) {
            wc->fetchByCoordinates(sLat, sLon, sCity, "zh");
        }

        // （可选）如果你也想立刻刷新 AQI，这里可以复用你在 InitW1 里那段 AQI 请求逻辑

        dlg.accept();
    });
    connect(btns, &QDialogButtonBox::rejected, &dlg, &QDialog::reject);

    dlg.exec();
}

void Widget::InitW4(QWidget *parent)
{
    // 仅放一个状态文本，不做任何视频显示
    QWidget* body = paneBody(parent);
    if (!body) return;

    auto v = qobject_cast<QVBoxLayout*>(body->layout());
    if (!v) { v = new QVBoxLayout(body); body->setLayout(v); }
    v->setContentsMargins(8,8,8,8);
    v->setSpacing(8);

    m_w4Status = new QLabel(QStringLiteral("准备推流…"), body);
    QFont f = m_w4Status->font(); f.setPointSize(12); f.setBold(true); m_w4Status->setFont(f);
    m_w4Status->setStyleSheet("color:#E0FFF7; background:transparent;");
    m_w4Status->setAlignment(Qt::AlignCenter);

    v->addStretch(1);
    v->addWidget(m_w4Status, 0, Qt::AlignCenter);
    v->addStretch(2);
}

    void Widget::setW4Status(const QString& text)
    {
        if (m_w4Status) m_w4Status->setText(text);
    }

    // 停止推流
    void Widget::stopRtmpPush()
    {
        if (m_streamRetry) {
            m_streamRetry->stop();
            m_streamRetry->deleteLater();
            m_streamRetry = nullptr;
        }
        if (m_streamProc) {
            m_streamProc->kill();
            m_streamProc->waitForFinished(1500);
            m_streamProc->deleteLater();
            m_streamProc = nullptr;
        }
        setW4Status(QStringLiteral("未推流"));
    }

    void Widget::startRtmpPush()
    {
        // 先停掉旧进程，释放 /dev/videoX
        stopRtmpPush();

        // 设备与地址（沿用你现有成员变量；没有就用默认）
        const QString dev = m_videoDev.isEmpty()
                ? QStringLiteral("/dev/%1").arg(QString(VIDEO))     // 例：VIDEO 宏是 "video1"
                : m_videoDev;
        const QString url = m_rtmpUrl.isEmpty()
                ? QStringLiteral(""
                                 "/%1").arg(m_farmId)
                : m_rtmpUrl;

        // ===== 编码参数（低一点保证 speed>=1.0x；延迟更稳）=====
              const int W   = 640;     // 可按需降到 426
              const int H   = 480;     // 可按需降到 240
              const int FPS = 10;      // 可按需降到 8
              const int GOP = 10;      // 1 秒一个关键帧（FPS 改了，GOP 跟着改）
              const int BR  = 600;     // kbps；带宽/CPU 吃紧可再降到 400
              const int VBV = 300;     // bufsize（kb）= BR 的一半，尽量小

              // ffmpeg 命令（和你在终端测试一致）
              const QString cmd = QString(
                "ffmpeg -f v4l2 -input_format yuyv422 -video_size %1x%2 -framerate %3 -i /dev/video1 -vcodec libx264 -preset ultrafast -b:v 1M -g 25 -f flv rtmp://120.26.201.192/live/%4"
              ).arg(W).arg(H).arg(FPS).arg(m_farmId);

        // 启动进程
        m_streamProc = new QProcess(this);
        m_streamProc->setProcessChannelMode(QProcess::MergedChannels);

        // 输出到日志（你原来的连接保持不变）
        connect(m_streamProc, &QProcess::readyReadStandardOutput, this, [this]{
            const QByteArray out = m_streamProc->readAllStandardOutput();
            if (!out.isEmpty()) qInfo().noquote() << "[RTMP]" << QString::fromLocal8Bit(out).trimmed();
        });
        connect(m_streamProc, &QProcess::readyReadStandardError, this, [this]{
            const QByteArray err = m_streamProc->readAllStandardError();
            if (!err.isEmpty()) qWarning().noquote() << "[RTMP-err]" << QString::fromLocal8Bit(err).trimmed();
        });

        // 退出后 5 秒重试（更快恢复）；这里会保持极低延迟参数
        connect(m_streamProc,
                QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished),
                this, [this](int code, QProcess::ExitStatus st){
            qWarning().noquote() << "[RTMP] exited, code=" << code << "status=" << st;
            setW4Status(QStringLiteral("推流已停止（%1）").arg(code));
            if (!m_streamRetry) {
                m_streamRetry = new QTimer(this);
                m_streamRetry->setSingleShot(true);
                connect(m_streamRetry, &QTimer::timeout, this, &Widget::startRtmpPush);
            }
            m_streamRetry->start(5000); // 5s 后重试
        });

        setW4Status(QStringLiteral("低延迟推流中：→ %1").arg(url));
        // 用 /bin/sh -lc 让引号/x264-params 正常生效
        m_streamProc->start(QStringLiteral("/bin/sh"), QStringList() << "-lc" << cmd);
    }




    bool Widget::hasGstElement(const char* name) {
        QProcess p;
        p.start(QStringLiteral("/bin/sh"),
                QStringList() << "-lc" << QString("gst-inspect-1.0 %1 >/dev/null 2>&1").arg(QString::fromLatin1(name)));
        p.waitForFinished(2000);
        return (p.exitStatus() == QProcess::NormalExit && p.exitCode() == 0);
    }

    QString Widget::pickH264Encoder() {
        // 优先级：硬编 v4l2h264enc > x264enc > openh264enc > avenc_h264（libav）
        if (hasGstElement("v4l2h264enc"))  return "v4l2h264enc";
        if (hasGstElement("x264enc"))      return "x264enc";
        if (hasGstElement("openh264enc"))  return "openh264enc";
        if (hasGstElement("avenc_h264"))   return "avenc_h264";
        return QString(); // 一个都没有
    }

    bool Widget::hasProgram(const char* name) {
        QProcess p;
        p.start("/bin/sh", {"-lc", QString("command -v %1 >/dev/null 2>&1").arg(name)});
        p.waitForFinished(1500);
        return p.exitStatus() == QProcess::NormalExit && p.exitCode() == 0;
    }

    QString Widget::pickFfmpegH264Encoder() {
        if (!hasProgram("ffmpeg")) return {};
        auto hasEnc = [&](const char* enc){
            QProcess p;
            p.start("/bin/sh", {"-lc", QString("ffmpeg -hide_banner -encoders | grep -q ' %1 '").arg(enc)});
            p.waitForFinished(3000);
            return p.exitStatus()==QProcess::NormalExit && p.exitCode()==0;
        };
        // ⚠️ 先用软件编码，避免 v4l2m2m 设备不存在的运行时错误
        if (hasEnc("libx264"))      return "libx264";
        if (hasEnc("h264_v4l2m2m")) return "h264_v4l2m2m";
        if (hasEnc("h264_omx"))     return "h264_omx";
        return {};
    }

    void Widget::showEditIntroDialog()
    {
        // 找到面板里的简介框（InitW9 里起的名）
        auto intro = this->findChild<QTextEdit*>("w9_intro");

        // 读当前文本，作为默认内容（= 原文）
        const QString curText = intro ? intro->toPlainText() : QString();

        // 对话框
        QDialog dlg(this);
        dlg.setWindowTitle(QStringLiteral("更改基地简介"));
        dlg.setModal(true);

        auto v = new QVBoxLayout(&dlg);
        v->setContentsMargins(16,16,16,16);
        v->setSpacing(12);

        // 可编辑的文本框（可滚动）
        auto ed = new QTextEdit(&dlg);
        ed->setPlainText(curText.isEmpty()
                         ? QStringLiteral("       智能大棚用传感器、 自动控\n"
                                          "制与云端数据分析,   实现对作物\n"
                                          "生长环境的精细化管理。系统实\n"
                                          "时采集数据，并联动设备，维持\n"
                                          "最优生长区间，降低人工成本。")
                         : curText);
        ed->setMinimumSize(420, 260);                  // 留点编辑空间
        ed->setLineWrapMode(QTextEdit::WidgetWidth);   // 自动换行
        v->addWidget(ed, 1);

        auto btns = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, &dlg);
        v->addWidget(btns);

        connect(btns, &QDialogButtonBox::accepted, &dlg, [&]{
            if (intro) intro->setText(ed->toPlainText());   // 回写到面板
            dlg.accept();
        });
        connect(btns, &QDialogButtonBox::rejected, &dlg, &QDialog::reject);

        dlg.exec();
    }

    void Widget::InitW7(QWidget *parent)
    {
        QWidget* body = paneBody(parent);
        if (!body) return;

        auto grid = new QGridLayout(body);
        grid->setContentsMargins(6, 2, 6, 2);
        grid->setHorizontalSpacing(12);
        grid->setVerticalSpacing(4);
        grid->setAlignment(Qt::AlignTop);
        body->setLayout(grid);

        // 一个格子：上-名称，下-开/关（置中）
        auto makeCell = [&](const QString& title, QLabel** valueOut)->QWidget* {
            QWidget* cell = new QWidget(body);
            auto v = new QVBoxLayout(cell);
            v->setContentsMargins(2, 0, 2, 0);
            v->setSpacing(2);

            QLabel* name = new QLabel(title, cell);
            QFont f = name->font(); f.setPointSize(13); f.setBold(false); name->setFont(f);
            name->setStyleSheet("color:#E0FFF7;");
            name->setAlignment(Qt::AlignHCenter | Qt::AlignBottom);

            QLabel* val = new QLabel(cell);
            setOnOffLabel(val, false);                 // 用你现有的胶囊样式
            val->setAlignment(Qt::AlignHCenter | Qt::AlignTop);

            v->addWidget(name, 0, Qt::AlignHCenter);
            v->addWidget(val,  0, Qt::AlignHCenter);

            if (valueOut) *valueOut = val;
            return cell;
        };

        int r = 0;
        // 第1行：升温 / 降温
        grid->addWidget(makeCell(QStringLiteral("升温设备"), &m_devHeat), r, 0);
        grid->addWidget(makeCell(QStringLiteral("降温设备"), &m_devCool), r, 1);
        ++r;
        // 第2行：通风 / 加湿
        grid->addWidget(makeCell(QStringLiteral("通风口"),   &m_devVent), r, 0);
        grid->addWidget(makeCell(QStringLiteral("加湿器"),   &m_devHum),  r, 1);
        ++r;
        // 第3行：天窗 / 白炽灯
        grid->addWidget(makeCell(QStringLiteral("大棚天窗"), &m_devSky),  r, 0);
        grid->addWidget(makeCell(QStringLiteral("白炽灯"),   &m_devLamp), r, 1);

        grid->setColumnStretch(0, 1);
        grid->setColumnStretch(1, 1);
        for (int i = 0; i < 3; ++i) grid->setRowStretch(i, 0);

        updateDeviceStateUi(0); // 初始全关
    }



    quint8 Widget::parseBitsToMask(const QString& bits) const {
        const QString s = bits.trimmed();

        // 二进制：1~8 位都接受，左侧补零到 8 位
        static const QRegularExpression rxBin(QStringLiteral("^[01]{1,8}$"));
        if (rxBin.match(s).hasMatch()) {
            const QString b = s.rightJustified(8, QLatin1Char('0')); // 高位补 0
            quint8 v = 0;
            for (int i = 0; i < 8; ++i) {
                if (b.at(i) == QLatin1Char('1')) v |= (1u << (7 - i)); // 左边是高位
            }
            return static_cast<quint8>(v & 0x3F);  // 只保留低 6 位（b5..b0）
        }

        // 十进制：0..255，随后同样只保留低 6 位
        bool ok = false;
        uint n = s.toUInt(&ok, 10);
        if (ok) return static_cast<quint8>(n & 0x3F);

        return 0;
    }

    void Widget::updateDeviceStateUi(quint8 mask) {
        // 位定义： b5 升温, b4 降温, b3 通风, b2 加湿, b1 天窗, b0 白炽灯
        setOnOffLabel(m_devHeat, (mask >> 5) & 1);
        setOnOffLabel(m_devCool, (mask >> 4) & 1);
        setOnOffLabel(m_devVent, (mask >> 3) & 1);
        setOnOffLabel(m_devHum,  (mask >> 2) & 1);
        setOnOffLabel(m_devSky,  (mask >> 1) & 1);
        setOnOffLabel(m_devLamp, (mask >> 0) & 1);
    }

    void Widget::onDeviceBits(const QString& bits) {
        const quint8 m = parseBitsToMask(bits);
        qInfo().noquote() << "[UI] device bits =" << bits << "mask=" << m;
        updateDeviceStateUi(m);
    }

    void Widget::onDeviceMask(quint8 mask) {
        qInfo().noquote() << "[UI] device mask =" << mask;
        updateDeviceStateUi(mask);
    }



//120.26.201.192


